package com.tomtop.core.dorequest;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.tomtop._const.Const;
import com.tomtop.core.utils.JSONUtils;
import com.tomtop.core.utils.ListUtils;
import com.tomtop.core.utils.MapUtils;
import com.tomtop.core.utils.StringUtils;

@Component
public class HttpRequestHandle {
	private final static Logger LOGGER = LoggerFactory.getLogger(HttpRequestHandle.class);
	@Autowired(required = false)
	RestTemplate restTemplate = new RestTemplate();

	private final static String toString(Object obj) {
		if (obj instanceof Number || obj instanceof Boolean)
			return String.valueOf(obj);
		if (obj instanceof String)
			return (String) obj;
		return JSONUtils.toJSONString(obj);
	}

	private final static String toArrayString(List<Integer> params, Object[] args) {
		if (params.isEmpty())
			return "";
		if (params.size() == 0)
			return toString(args[params.get(0)]);
		String ret = "";
		for (Integer id : params) {
			ret += "," + toString(args[id]);
		}
		return ret.substring(1);
	}

	private final static Object toJsonObject(List<Integer> params, Object[] args) {
		if (params.isEmpty())
			return "";
		if (params.size() == 0)
			return args[params.get(0)];
		Object[] objs = new Object[params.size()];
		for (int i = 0; i < objs.length; i++) {
			objs[i] = args[params.get(i)];
		}
		return objs;
	}

	private final static String encodeArrayString(List<Integer> params, Object[] args) {
		try {
			return URLEncoder.encode(toArrayString(params, args), Const.DEFAULT_CHARSET.name());
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}

	private final static Pattern urlPathVariablePattern = Pattern.compile("\\{\\{([\\w _]*)\\}\\}+");// 解析所有{{。。。}}标识的资源
	private final static Map<String, Map<String, String>> cacheKeys = new LinkedHashMap<>();

	private final static Map<String, String> getKeys(String urlpath) {
		if (cacheKeys.containsKey(urlpath))
			return cacheKeys.get(urlpath);
		Matcher matcher = urlPathVariablePattern.matcher(urlpath);
		Map<String, String> keys = new LinkedHashMap<>();
		while (matcher.find()) {
			String key = matcher.group();
			keys.put(key, key.substring(2, key.length() - 2).trim());
		}
		cacheKeys.put(urlpath, keys);
		return keys;
	}

	private final static StringBuffer generateHost(String scheme, String hostKey) {
		HostName hostName = HttpParamConfig.getByName(hostKey);
		StringBuffer uri = new StringBuffer().append(scheme).append("://");
		if (hostName == null)
			return uri.append(hostKey);
		uri.append(hostName.getHost());
		if (hostName.getPort() > 80) {
			uri.append(":").append(hostName.getPort());
		}
		return uri;
	}

	private final static String parsePathVariable(String urlpath, Map<String, List<Integer>> paths, Object[] args) {
		Map<String, String> keys = getKeys(urlpath);
		String newurl = urlpath;
		if (!newurl.startsWith("/"))
			newurl = "/" + newurl;
		for (Map.Entry<String, String> entry : keys.entrySet()) {
			if (paths == null || !paths.containsKey(entry.getValue())) {
				newurl = newurl.replace(entry.getKey(), "");
			} else {
				List<Integer> params = paths.get(entry.getValue());
				newurl = newurl.replace(entry.getKey(), encodeArrayString(params, args));
			}
		}
		return newurl;
	}

	private final static StringBuffer parseParam(Map<String, List<Integer>> params, Object[] args) {
		StringBuffer uri = new StringBuffer();
		if (params == null || params.size() == 0)
			return uri;
		for (Map.Entry<String, List<Integer>> entry : params.entrySet()) {
			uri.append(entry.getKey());
			uri.append("=");
			uri.append(encodeArrayString(entry.getValue(), args));
			uri.append("&");
		}
		uri.deleteCharAt(uri.length() - 1);
		return uri;
	}

	private final static HttpHeaders parseHeader(Map<String, List<Integer>> headers, Object[] args) {
		HttpHeaders httpHeaders = new HttpHeaders();
		if (MapUtils.isNotEmpty(headers)) {
			headers.forEach((name, params) -> {
				httpHeaders.add(name, toArrayString(params, args));
			});
		}
		return httpHeaders;
	}

	private final static String parseCookie(Map<String, List<Integer>> cookies, Object[] args) {
		return MapUtils.n(cookies).array((key, params) -> (key + "=" + encodeArrayString(params, args))).join(";");
	}

	private final static String toJsonBodyString(Map<String, List<Integer>> bodys, Object[] args) {
		if (MapUtils.isEmpty(bodys))
			return "";
		if (bodys.size() == 1) {
			return JSONUtils.toJSONString(toJsonObject(bodys.values().iterator().next(), args));
		} else {
			return JSONUtils.toJSONString(MapUtils.n(bodys).map((name, params) -> toJsonObject(params, args)).to());
		}
	}

	@SuppressWarnings("unchecked")
	private final static String toFormBodyString(Map<String, List<Integer>> bodys, Object[] args) {
		if (MapUtils.isEmpty(bodys))
			return "";
		if (bodys.size() > 1) {
			return MapUtils.n(bodys).map((name, params) -> toJsonObject(params, args))
					.array((name, val) -> name + "=" + toString(val)).join("&");
		} else {
			List<Integer> params = bodys.values().iterator().next();
			Object obj = toJsonObject(params, args);

			if (obj instanceof Number || obj instanceof Boolean)
				return String.valueOf(obj);
			if (obj instanceof String)
				return (String) obj;
			if (obj instanceof Map)
				return MapUtils.n((Map<String, Object>) obj).array((name, val) -> name + "=" + toString(val)).join("&");
			if (obj instanceof Collection)
				return ListUtils.n((Collection<Object>) obj).join(",");
			if (obj.getClass().isArray())
				return ListUtils.<Object>n().a((Object[]) obj).join(",");
			BeanMap map = BeanMap.create(obj);
			return MapUtils.n(map).array((name, val) -> name + "=" + toString(val)).join("&");
		}
	}

	private URI getUri(String scheme, String hostKey, String urlpath, String version,
			Map<Integer, Map<String, List<Integer>>> variables, Object[] args) {

		Map<String, List<Integer>> params = variables.get(0);
		Map<String, List<Integer>> paths = variables.get(4);
		StringBuffer uri = generateHost(scheme, hostKey);
		uri.append(parsePathVariable(urlpath, paths, args));
		if (uri.indexOf("?") == -1)
			uri.append("?");
		else
			uri.append("&");
		uri.append(parseParam(params, args));
		if (StringUtils.isNotEmpty(version))
			uri.append("&version=").append(version);
		return URI.create(uri.toString());
	}

	public HttpContext handle(DoHttpRequestData requestData, Object[] args) {

		Map<Integer, Map<String, List<Integer>>> variables = requestData.getVariable();
		Map<String, List<Integer>> headers = variables.get(1);
		Map<String, List<Integer>> bodys = variables.get(2);
		Map<String, List<Integer>> cookies = variables.get(3);

		HttpHeaders header = parseHeader(headers, args);
		header.add(HttpHeaders.COOKIE, parseCookie(cookies, args));

		String body;
		if (requestData.isFrom()) {
			body = toFormBodyString(bodys, args);
			header.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
		} else {
			body = toJsonBodyString(bodys, args);
			header.setContentType(MediaType.APPLICATION_JSON_UTF8);
		}

		String scheme = requestData.getScheme();
		String hostKey = requestData.getHostKey();
		String urlpath = requestData.getPath();
		URI url = getUri(scheme, hostKey, urlpath, requestData.getVersion(), variables, args);

		HttpMethod method = requestData.getMethod();
		return send(method, body, header, url);
	}

	private HttpContext send(HttpMethod method, String body, HttpHeaders header, URI url) {
		RequestEntity<String> request = new RequestEntity<>(body, header, method, url);
		try {
			ResponseEntity<String> response = restTemplate.exchange(request, String.class);
			LOGGER.debug("请求的url {} http请求响应的对象  {}", url, response.getBody());
			return new HttpContext(request, response);
		} catch (Throwable throwable) {
			LOGGER.error("发请求{}, 异常消息{}", url, throwable.getMessage());
			LOGGER.debug("发请求出现了异常", throwable);
			return new HttpContext(request, throwable);
		}
	}
}
